﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.ComponentModel;
using System.Reflection;

/// <summary>This static class implements a pair of generic extension over the IEnumerable&lt;&gt;</summary>
public static partial class EnumerableEx
{
	/// <summary>Execute the action once for each element in the collection.</summary>
	/// <typeparam name="TSource">Elements</typeparam>
	/// <param name="src">The collection</param>
	/// <param name="act">The action to execute</param>
	public static void ForEach<TSource>( this IEnumerable<TSource> src, Action<TSource> act )
	{
		foreach( var i in src )
			act( i );
	}

	/// <summary>Iterate over the { E1 from seq1, E2 from seq2 } pairs of the elements.</summary>
	/// <typeparam name="E1"></typeparam>
	/// <typeparam name="E2"></typeparam>
	/// <param name="seq1"></param>
	/// <param name="seq2"></param>
	/// <param name="proc">The functor return value has the following meaning:
	/// 0 = go to both next E1 and E2
	/// &gt;0 = keep current E1 position, only advance the E2
	/// &lt;0 = advance the E1 position, keep current E2 position.</param>
	/// <returns>Count of the elements from seq1 and seq2 that were processed by proc.</returns>
	public static pair<int, int> SequentialJoin<E1, E2>( IEnumerable<E1> seq1, IEnumerable<E2> seq2, Func<E1, E2, int> proc )
	{
		IEnumerator<E1> enum1 = seq1.GetEnumerator();
		IEnumerator<E2> enum2 = seq2.GetEnumerator();
		
		if( !enum1.MoveNext() || !enum2.MoveNext() )
			return new pair<int, int>( 0, 0 );

		int n1 = 0, n2 = 0;
		while( true )
		{
			E1 elt1 = enum1.Current;
			E2 elt2 = enum2.Current;

			int res = proc( elt1, elt2 );
			if( 0 == res )
			{
				// proceed with both of them
				n1++;
				n2++;
				if( !enum1.MoveNext() )
					break;
				if( !enum2.MoveNext() )
					break;
			} else if( res < 0 )
			{
				// only proceed with sequence #1
				n1++;
				if( !enum1.MoveNext() )
					break;
			}
			else
			{
				// only proceed with sequence #2
				n2++;
				if( !enum2.MoveNext() )
					break;
			}
		}
		return new pair<int, int>( n1, n2 );
	}

	/// <summary>Merge two sorted sequences into single sorted sequence</summary>
	/// <remarks>If the input sequences were not sorted, the result is undefined.</remarks>
	/// <param name="seq0"></param>
	/// <param name="seq1"></param>
	/// <param name="comparer"></param>
	/// <returns></returns>
	public static IEnumerable<tElt> MergeSortedSequences<tElt>( IEnumerable<tElt> seq0, IEnumerable<tElt> seq1,
		Func<tElt, tElt, int> comparer )
	{
		IEnumerator<tElt>[] enums = new IEnumerator<tElt>[ 2 ]
		{
			seq0.GetEnumerator(),
			seq1.GetEnumerator()
		};

		if( !enums[ 0 ].MoveNext() )
		{
			// seq0 is empty, return the whole seq1
			while( enums[ 1 ].MoveNext() )
				yield return enums[ 1 ].Current;
			yield break;
		}

		if( !enums[ 1 ].MoveNext() )
		{
			// seq1 is empty, return the whole seq0
			yield return enums[ 0 ].Current;
			while( enums[ 0 ].MoveNext() )
				yield return enums[ 0 ].Current;
			yield break;
		}

		tElt[] curr = new tElt[ 2 ] { enums[ 0 ].Current, enums[ 1 ].Current };

		while(true)
		{
			int compareRes = comparer( curr[ 0 ], curr[ 1 ] );

			int iEnumToAdvance = ( compareRes > 0 ) ? 1 : 0;

			// Return one of the values
			yield return curr[ iEnumToAdvance ];

			// Try to advance one element in iEnumToAdvance
			if( enums[iEnumToAdvance].MoveNext() )
			{
				// Success
				curr[ iEnumToAdvance ] = enums[ iEnumToAdvance ].Current;
				continue;
			}

			// The iEnumToAdvance sequence is depleted.
			// Return the remaining elements in another one.

			int iEnumToFlush = ( iEnumToAdvance + 1 ) % 2;
			yield return curr[ iEnumToFlush ];
			while( enums[ iEnumToFlush ].MoveNext() )
				yield return enums[ iEnumToFlush ].Current;

			break;
		}
	}

	/// <summary>Selectively skip some elements from the input sequence based on their key uniqueness.
	/// If several elements share the same key value, return the 1-st one, skip the rest of them.</summary>
	/// <typeparam name="tSource"></typeparam>
	/// <typeparam name="tKey"></typeparam>
	/// <param name="src"></param>
	/// <param name="keySelecta"></param>
	/// <returns></returns>
	public static IEnumerable<tSource> uniqueBy<tSource, tKey>( this IEnumerable<tSource> src, Func<tSource, tKey> keySelecta )
	{
		HashSet<tKey> res = new HashSet<tKey>();
		foreach( tSource e in src )
		{
			tKey k = keySelecta( e );
			if( res.Contains( k ) )
				continue;
			res.Add( k );
			yield return e;
		}
	}

	/// <summary>Selectively skip some elements from the input sequence based on their uniqueness.
	/// If several elements are equal, return the 1-st one, skip the rest of them.</summary>
	/// <typeparam name="tSource"></typeparam>
	/// <param name="src"></param>
	/// <returns></returns>
	public static IEnumerable<tSource> unique<tSource>( this IEnumerable<tSource> src )
	{
		return src.uniqueBy( elt => elt );
	}

	/// <summary>Shuffle the elements randomly.</summary>
	/// <typeparam name="T"></typeparam>
	/// <param name="source"></param>
	/// <param name="rng"></param>
	/// <returns></returns>
	public static IEnumerable<T> Shuffle<T>( this IEnumerable<T> source, Random rng )
	{
		T[] elements = source.ToArray();
		// Note i > 0 to avoid final pointless iteration
		for( int i = elements.Length - 1; i > 0; i-- )
		{
			// Swap element "i" with a random earlier element it (or itself)
			int swapIndex = rng.Next( i + 1 );
			yield return elements[ swapIndex ];
			elements[ swapIndex ] = elements[ i ];
			// we don't actually perform the swap, we can forget about the
			// swapped element because we already returned it.
		}

		// there is one item remaining that was not returned - we return it now
		yield return elements[ 0 ];
	}

	/// <summary>Get all public properties of the specified object, along with their values.
	/// Indexed properties are skipped.</summary>
	/// <param name="obj"></param>
	/// <param name="t"></param>
	/// <returns></returns>
	static IEnumerable<pair<string, object>> GetProperties( object obj, Type t )
	{
		var pi = t.GetProperties( BindingFlags.Instance | BindingFlags.Public );
		return pi
			.Where( p => p.GetIndexParameters().GetLength( 0 ) == 0 )
			.Select( p => new pair<string, object>( p.Name, p.GetValue( obj, null ) ) );
	}

	/// <summary>Get all public fields of the specified object, along with their values.</summary>
	/// <param name="obj"></param>
	/// <param name="t"></param>
	/// <returns></returns>
	static IEnumerable<pair<string, object>> GetFields( object obj, Type t )
	{
		var fields = t.GetFields( BindingFlags.Instance | BindingFlags.Public );
		return fields.Select( f => new pair<string, object>( f.Name, f.GetValue( obj ) ) );
	}

	/// <summary>Return all public properties &amp; fields of the specified object as a collection of name-value pairs.
	/// The result is sorted by the name, case-agnostic.
	/// Indexed properties are not returned.</summary>
	/// <param name="obj"></param>
	/// <returns></returns>
	public static IEnumerable<pair<string, object>> GetPropertiesCollection( this object obj )
	{
		Type t = obj.GetType();
		return GetProperties( obj, t )
			.Concat( GetFields( obj, t ) )
			.OrderBy( nv => nv.first.ToLowerInvariant() );
	}

	// http://stackoverflow.com/questions/2471588
	///<summary>Finds the index of the first item matching an expression in an enumerable.</summary>
	///<param name="items">The enumerable to search.</param>
	///<param name="predicate">The expression to test the items against.</param>
	///<returns>The index of the first matching item, or -1 if no items match.</returns>
	public static int FindIndex<T>( this IEnumerable<T> items, Func<T, bool> predicate )
	{
		if( items == null ) throw new ArgumentNullException( "items" );
		if( predicate == null ) throw new ArgumentNullException( "predicate" );

		int retVal = 0;
		foreach( var item in items )
		{
			if( predicate( item ) ) return retVal;
			retVal++;
		}
		return -1;
	}

	/// <summary>Put all elements to the hashset.</summary>
	public static HashSet<T> ToHashSet<T>( this IEnumerable<T> items )
	{
		HashSet<T> res = new HashSet<T>();
		items.ForEach( i =>
		{
			if( res.Contains( i ) )
				return;
			res.Add( i );
		} );
		return res;
	}

	/// <summary>Concatenate the nested IEnumerable's into a single IEnumerable.</summary>
	public static IEnumerable<T> Concat2<ET, T>( this IEnumerable<ET> items )
		where ET: IEnumerable<T>
	{
		foreach( var ie in items )
			foreach( var i in ie )
				yield return i;
	}

	/// <summary>Return IEnumerable that lists elements in the reverse order.
	/// Unlike LINQ's reverse(), this one is O(1).</summary>
	/// <typeparam name="tElt"></typeparam>
	/// <param name="list"></param>
	/// <returns></returns>
	public static IEnumerable<tElt> Reverse<tElt>( this IList<tElt> list )
	{
		for( int i = list.Count - 1; i >= 0; i-- )
			yield return list[ i ];
	}
}